﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Xml.Serialization;
using System.Collections;
using System.ComponentModel;

namespace ObjectComposition
{
    public class Entity
    {
        public struct Handle
        {
            [XmlAttribute]
            private string id;
            public string ID
            {
                get
                {
                    return this.target != null ? this.target.ID : this.id;
                }
                set
                {
                    this.id = value;
                }
            }

            private Entity target;

            [XmlIgnore]
            public Entity Target
            {
                get
                {
                    if (!string.IsNullOrEmpty(this.ID) && this.target == null)
                    {
                        string id = this.ID;
                        this.target = Entity.entities.FirstOrDefault(x => x.ID == id);
                    }
                    return this.target;
                }
                set
                {
                    this.target = value;
                    this.ID = this.target == null ? null : this.target.ID;
                }
            }

            public static implicit operator Entity(Handle obj)
            {
                return obj.Target;
            }

            public static implicit operator Handle(Entity obj)
            {
                return new Handle { Target = obj, ID = obj == null ? null : obj.ID };
            }
        }

        public enum EntityType { Player, Map, Enemy };

        protected static List<Entity> entities = new List<Entity>();

        protected static string generateEntityID()
        {
            return Guid.NewGuid().ToString();
        }
        
        [XmlAttribute]
        public EntityType Type { get; set; }

        [XmlIgnore]
        public string ID
        {
            get
            {
                return this.idProperty.Value;
            }
            set
            {
                this.idProperty.Value = value;
            }
        }

		private bool serialize = true;
        [XmlIgnore]
        public bool Serialize
		{
			get
			{
				return this.serialize;
			}
			set
			{
				this.serialize = value;
			}
		}

        [XmlAttribute]
        [DefaultValue(0)]
        public int DrawOrder { get; set; }
        
        protected Main main;
        protected Dictionary<string, Component> components = new Dictionary<string, Component>();
        protected Dictionary<string, IProperty> properties = new Dictionary<string, IProperty>();
        protected Property<string> _idProperty;
        protected Property<string> idProperty
        {
            get
            {
                if (this._idProperty == null)
                {
                    this._idProperty = this.GetProperty<string>("ID");
                    if (this._idProperty == null)
                    {
                        this._idProperty = new Property<string> { Editable = true, Value = Entity.generateEntityID() };
                        this.Add("ID", this._idProperty);
                    }
                }
                return this._idProperty;
            }
        }

        [XmlIgnore]
        protected Dictionary<string, Command> commands = new Dictionary<string, Command>();

        public IEnumerable<Component> ComponentList
        {
            get
            {
                return this.components.Values;
            }
        }

        public IEnumerable<IProperty> PropertyList
        {
            get
            {
                return this.properties.Values;
            }
        }

        [XmlArray("Components")]
        [XmlArrayItem("Component", Type = typeof(DictionaryEntry))]
        public DictionaryEntry[] Components
        {
            get
            {
                // Make an array of DictionaryEntries to return
                IEnumerable<KeyValuePair<string, Component>> serializableComponents = this.components.Where(x => x.Value.Serialize);
                DictionaryEntry[] ret = new DictionaryEntry[serializableComponents.Count()];
                int i = 0;
                DictionaryEntry de;
                // Iterate through properties to load items into the array.
                foreach (KeyValuePair<string, Component> component in serializableComponents)
                {
                    de = new DictionaryEntry();
                    de.Key = component.Key;
                    de.Value = component.Value;
                    ret[i] = de;
                    i++;
                }
                return ret;
            }
            set
            {
                this.components.Clear();
                for (int i = 0; i < value.Length; i++)
                {
                    this.components.Add((string)value[i].Key, (Component)value[i].Value);
                }
            }
        }

        [XmlArray("Properties")]
        [XmlArrayItem("Property", Type = typeof(DictionaryEntry))]
        public DictionaryEntry[] Properties
        {
            get
            {
                // Make an array of DictionaryEntries to return
                DictionaryEntry[] ret = new DictionaryEntry[this.properties.Count];
                int i = 0;
                DictionaryEntry de;
                // Iterate through properties to load items into the array.
                foreach (KeyValuePair<string, IProperty> property in this.properties)
                {
                    de = new DictionaryEntry();
                    de.Key = property.Key;
                    de.Value = property.Value;
                    ret[i] = de;
                    i++;
                }
                return ret;
            }
            set
            {
                for (int i = 0; i < value.Length; i++)
                {
                    this.properties.Add((string)value[i].Key, (IProperty)value[i].Value);
                }
            }
        }

        public Entity()
        {
            // Called by XmlSerializer
            Entity.entities.Add(this);
        }

        public Entity(Main _main, EntityType _type)
            : this()
        {
            // Called by a Factory
            this.Add("ID", new Property<string> { Editable = true, Value = Entity.generateEntityID() });
            this.Serialize = true;
            this.main = _main;
            this.Type = _type;
        }

        public void SetMain(Main _main)
        {
            this.main = _main;
            foreach (Component c in this.components.Values)
            {
                c.SetEntity(this.main, this);
                c.InitializeProperties();
            }
        }

        public void Add(string name, Command cmd)
        {
            this.commands.Add(name, cmd);
        }

        public void Add(string name, Component component)
        {
            this.components.Add(name, component);
        }

        public void Add(string name, IProperty property)
        {
            this.properties.Add(name, property);
        }

        public void RemoveProperty(string name)
        {
            this.properties.Remove(name);
        }

        public void RemoveComponent(string name)
        {
            this.components.Remove(name);
        }

        public void Update(GameTime gameTime)
        {
            foreach (Component component in this.components.Values)
            {
                component.Update(this.main.ElapsedTime);
            }
        }

        public void Draw(GameTime time)
        {
            foreach (Component component in this.components.Values)
                component.Draw(time);
        }

        public Property<T> GetProperty<T>()
        {
            return this.properties.Values.OfType<Property<T>>().FirstOrDefault();
        }

        public T GetComponent<T>() where T : Component
        {
            return this.components.Values.OfType<T>().FirstOrDefault();
        }

        public Property<T> GetProperty<T>(string name)
        {
            try
            {
                return (Property<T>)this.properties[name];
            }
            catch (KeyNotFoundException)
            {
                return null;
            }
        }

        public T GetComponent<T>(string name) where T : Component
        {
            try
            {
                return (T)this.components[name];
            }
            catch (KeyNotFoundException)
            {
                return null;
            }
        }

        public Command<T> GetCommand<T>(string name)
        {
            return (Command<T>)this.commands[name];
        }

        public Command<T, T2> GetCommand<T, T2>(string name)
        {
            return (Command<T, T2>)this.commands[name];
        }

        public Command<T, T2, T3> GetCommand<T, T2, T3>(string name)
        {
            return (Command<T, T2, T3>)this.commands[name];
        }

        public void Delete()
        {
            foreach (Component component in this.components.Values)
                component.Delete();
            this.main.Remove(this);
            Entity.entities.Remove(this);
        }
    }
}
